package rahmath.sharif.docrypt.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;

import rahmath.sharif.docrypt.DocryptInterface;
import rahmath.sharif.docrypt.Explorer;
import rahmath.sharif.docrypt.R;
import rahmath.sharif.docrypt.constant.DocryptConstants;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.widget.CheckBox;
import android.widget.Toast;

public class Docrypt extends AsyncTask<String, Integer, String> implements
		DocryptConstants {

	private DocryptInterface docryptInterface;
	private String password;
	private File sourceFile;
	private File[] sourceFiles;
	private File destinationFile;
	private int mode;
	private Properties fileNameMap;
	private Properties mimeMap;

	private int recursionCounter;
	private List<File> destinationFiles;

	private long total;
	private long fileLength;

	private boolean isDrive;

	public Docrypt(DocryptInterface docryptInterface, String password,
			File sourceFile, File destinationFile, int mode) {
		this.docryptInterface = docryptInterface;
		this.password = password;
		this.sourceFile = sourceFile;
		this.destinationFile = destinationFile;
		this.mode = mode;
	}

	public Docrypt(DocryptInterface docryptInterface, String password,
			File[] sourceFiles, File destinationFile, int mode,
			Properties fileNameMap, Properties mimeMap) {
		this.docryptInterface = docryptInterface;
		this.password = password;
		this.sourceFiles = sourceFiles;
		this.destinationFile = destinationFile;
		this.mode = mode;
		this.fileNameMap = fileNameMap;
		this.mimeMap = mimeMap;

		this.destinationFiles = new ArrayList<File>(this.sourceFiles.length);
	}

	@Override
	protected String doInBackground(String... strings) {
		if (null == this.sourceFile) {
			isDrive = true;
			setFileLength(this.sourceFiles);
			docrypt(this.sourceFiles, this.destinationFile);
			if (this.docryptInterface instanceof Explorer) {
				((Explorer) this.docryptInterface).persistProperties();
			}
		} else {
			docrypt(this.sourceFile, this.destinationFile);
		}
		return null;
	}

	private void setFileLength(File[] sourceFiles) {
		if (null != sourceFiles) {
			for (File file : sourceFiles) {
				if (file.isDirectory()) {
					setFileLength(file.listFiles());
				} else {
					this.fileLength += file.length();
				}
			}
		}
	}

	private void docrypt(File[] sourceFiles, File destinationFolder) {
		recursionCounter++;
		if (isCancelled())
			return;
		if (null != sourceFiles) {
			for (File sourceFile : sourceFiles) {
				if (isCancelled())
					break;
				File destinationFile = null;
				if (this.mode == Cipher.ENCRYPT_MODE) {
					String randomId = UUID.randomUUID().toString();
					destinationFile = new File(destinationFolder, randomId);
					fileNameMap.setProperty(destinationFile.getAbsolutePath(),
							sourceFile.getName());
					mimeMap.setProperty(destinationFile.getAbsolutePath(),
							Integer.toString(MimeUtil
									.getDrawableForFile(sourceFile)));
				} else if (this.mode == Cipher.DECRYPT_MODE) {
					destinationFile = new File(destinationFolder,
							fileNameMap.getProperty(sourceFile
									.getAbsolutePath()));
				}
				if (1 == this.recursionCounter) {
					this.destinationFiles.add(destinationFile);
				}
				if (sourceFile.isDirectory()) {
					destinationFile.mkdirs();
					docrypt(sourceFile.listFiles(), destinationFile);
				} else {
					docrypt(sourceFile, destinationFile);
				}
			}
		}
	}

	public void docrypt(File sourceFile, File destinationFile) {
		FileInputStream fileInputStream = null;
		FileOutputStream fileOutputStream = null;
		CipherOutputStream cipherOutputStream = null;
		try {
			fileInputStream = new FileInputStream(sourceFile);
			fileOutputStream = new FileOutputStream(destinationFile);
			SecretKeySpec skeySpec = new SecretKeySpec(AESCryptor.getRawKey(
					256, this.password.getBytes(UTF8)), ALGORITHM);
			Cipher cipher = Cipher.getInstance(ALGORITHM);
			cipher.init(this.mode, skeySpec);
			cipherOutputStream = new CipherOutputStream(fileOutputStream,
					cipher);
			byte[] input = new byte[40960];
			int bytesRead;
			long fileLength = sourceFile.length();
			long total = 0;
			while ((bytesRead = fileInputStream.read(input)) != -1) {
				if (isCancelled())
					break;
				total += bytesRead;
				if (this.isDrive) {
					this.total += bytesRead;
					publishProgress((int) (this.total * 100 / this.fileLength),
							(int) (total * 100 / fileLength));
				} else {
					publishProgress((int) (total * 100 / fileLength));
				}
				cipherOutputStream.write(input, 0, bytesRead);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (fileInputStream != null)
					fileInputStream.close();
				if (cipherOutputStream != null) {
					cipherOutputStream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	@Override
	protected void onPreExecute() {
		super.onPreExecute();
		this.docryptInterface.getProgressDialog().show();
	}

	@Override
	protected void onProgressUpdate(Integer... progress) {
		super.onProgressUpdate(progress);
		this.docryptInterface.getProgressDialog().setProgress(progress[0]);
		if (isDrive) {
			this.docryptInterface.getProgressDialog().setSecondaryProgress(
					progress[1]);
		}
	}

	@Override
	protected void onPostExecute(String result) {
		super.onPostExecute(result);
		if (null != ((CheckBox) docryptInterface.getActivity().findViewById(
				R.id.checkBoxDeleteSourceOnCompletion))
				&& ((CheckBox) docryptInterface.getActivity().findViewById(
						R.id.checkBoxDeleteSourceOnCompletion)).isChecked()) {
			if (sourceFile.delete()) {
				Toast.makeText(
						docryptInterface.getActivity().getApplicationContext(),
						docryptInterface.getActivity().getString(
								R.string.processCompletedAndSourceDeleted),
						Toast.LENGTH_LONG).show();
			} else {
				Toast.makeText(
						docryptInterface.getActivity().getApplicationContext(),
						docryptInterface.getActivity().getString(
								R.string.processCompletedAndSourceNotDeleted),
						Toast.LENGTH_LONG).show();
			}
		} else {
			Toast.makeText(
					docryptInterface.getActivity().getApplicationContext(),
					docryptInterface.getActivity().getString(
							R.string.processCompleted), Toast.LENGTH_LONG)
					.show();
		}
		docryptInterface.getProgressDialog().dismiss();
		if (this.mode == Cipher.ENCRYPT_MODE
				&& this.docryptInterface instanceof Explorer) {
			((Explorer) this.docryptInterface).refresh();
		}
	}

	@Override
	protected void onCancelled() {
		super.onCancelled();
		if (null != ((CheckBox) docryptInterface.getActivity().findViewById(
				R.id.checkBoxDeleteSourceOnCompletion))
				&& ((CheckBox) docryptInterface.getActivity().findViewById(
						R.id.checkBoxDeleteSourceOnCompletion)).isSelected()) {
			if (this.sourceFile.exists()) {
				if (this.destinationFile.delete()) {
					Toast.makeText(
							docryptInterface.getActivity()
									.getApplicationContext(),
							docryptInterface.getActivity().getString(
									R.string.processCancelled),
							Toast.LENGTH_LONG).show();
				}
			} else {
				if (this.destinationFile.exists()) {
					Toast.makeText(
							docryptInterface.getActivity()
									.getApplicationContext(),
							docryptInterface.getActivity().getString(
									R.string.processCompletedAndSourceDeleted),
							Toast.LENGTH_LONG).show();
				}
			}
		} else {
			if (this.mode == Cipher.ENCRYPT_MODE
					&& this.docryptInterface instanceof Explorer) {
				final File[] destinationFilesArray = new File[this.destinationFiles
						.size()];
				final ProgressDialog progressDialog = ProgressDialog.show(
						Docrypt.this.docryptInterface.getActivity(), "",
						"Processing. Please wait...", true);
				new Thread(new Runnable() {
					public void run() {
						final boolean success = DocryptUtil.deleteFiles(
								Docrypt.this.destinationFiles
										.toArray(destinationFilesArray),
								Docrypt.this.fileNameMap, Docrypt.this.mimeMap);
						Docrypt.this.docryptInterface.getActivity()
								.runOnUiThread(new Runnable() {
									public void run() {
										progressDialog.dismiss();
										if (!success) {
											Toast.makeText(
													Docrypt.this.docryptInterface
															.getActivity(),
													"Some of the files are not deleted.",
													Toast.LENGTH_SHORT).show();
										}
										((Explorer) Docrypt.this.docryptInterface)
												.refresh();
									}
								});
					}
				}).start();
			} else if (this.destinationFile.delete()) {
				Toast.makeText(
						docryptInterface.getActivity().getApplicationContext(),
						docryptInterface.getActivity().getString(
								R.string.processCancelled), Toast.LENGTH_LONG)
						.show();
			} else {
				Toast.makeText(
						docryptInterface.getActivity().getApplicationContext(),
						docryptInterface
								.getActivity()
								.getString(
										R.string.processCancelledAndOutputFileNotDeleted),
						Toast.LENGTH_LONG).show();
			}
		}
	}
}